#include "app/Application.h"
#include "app/ApplicationConfig.h"
#include "gui/objects/GUIMap.h"
#include "gui/objects/GUIRoute.h"
#include "gui/GUIMapFactory.h"
#include "map/controllers/RouteController.h"
#include "map/exceptions/RoadNotFoundException.h"
#include "map/exceptions/UnexistingAddressException.h"
#include "map/route/PointToPointQuery.h"
#include "map/Map.h"
#include "map/MapFactory.h"
#include "parser/exceptions/InvalidDataException.h"
#include "util/exceptions/ElementNotFoundException.h"
#include "util/logger/LoggerManager.h"
#include "util/MathUtils.h"
#include "util/StringUtils.h"
namespace app {

Application::Application(std::string configPath) {

	// se levanta la configuracion del archivo de configuracion
	app::ApplicationConfig::loadConfigFromFile(configPath + "/Configuracion.txt");

	std::string mapDataPath = app::ApplicationConfig::getOptionalProperty("mapDataPath", "res");

	// se levanta la configuracion del archivo de configuracion
	app::ApplicationConfig::loadConfigFromFile(mapDataPath + "/RecorridoSolicitado.txt");
}

Application::~Application() {}

void Application::run(util::HashMap<std::string, std::string>* parameters) throw (util::Exception) {
	// Se crea el Mapa del modelo
	MapFactory factory;
	std::string mapDataPath = app::ApplicationConfig::getOptionalProperty("mapDataPath", "res");
	Map* map = factory.createMap(mapDataPath);
	this->fillMissingParametersFromConfig(parameters);
	bool calculateRoute = true;
	QueryRoute* qRoute = NULL;

	// TODO workaround porque el mapa no acepta const char* por std::string.
	std::string paramName("originName");
	std::string originName, destinationName;
	int originNumber, destinationNumber;
	RouteOptimize opt;
	try {
		originName = *parameters->get(&paramName);
		paramName = "originNumber";
		originNumber = util::stringToInt(*parameters->get(&paramName));
		paramName = "destinationName";
		destinationName = *parameters->get(&paramName);
		paramName = "destinationNumber";
		destinationNumber = util::stringToInt(*parameters->get(&paramName));
		paramName = "routeOptimize";
		std::string* routeOptimize = parameters->get(&paramName);
		switch (routeOptimize->c_str()[0]) {
		case 'T': opt = Time; break;
		default:  opt = Distance; break;
		}
	} catch (util::ElementNotFoundException &e) {
		logger()->warn("Los parametros ingresados son insuficientes, el camino no se va a dibujar: " + e.getMessage());
		calculateRoute = false;
	} catch (parser::InvalidDataException &e) {
		logger()->warn("Los parametros ingresados son invalidos, el camino no se va a dibujar: " + e.getMessage());
		calculateRoute = false;
	}

	if (calculateRoute) {
		// Se intenta calcular el camino minimo (se lo pide al mapa).
		try {
			qRoute = new PointToPointQuery(map, originName, originNumber, destinationName, destinationNumber, opt);
		} catch (UnexistingAddressException &e) {
			logger()->warn(e.getMessage() + "\n\tEl camino no se va a dibujar.");
		} catch (RoadNotFoundException &e) {
			logger()->warn(e.getMessage() + "\n\tEl camino no se va a dibujar.");
		}
	}

	// Se crea un Mapa grafico usando el Mapa del modelo
	gui::GUIMapFactory guiMapFactory;
	gui::GUIMap* guiMap = guiMapFactory.createGUIMap(map);
	RouteController* routeController = new RouteController(qRoute);
	gui::GUIRoute* guiRoute = new gui::GUIRoute(routeController);

	// se crea una aplicacion grafica (ventana + motor grafico)
	this->guiApp = new gui::GUIApplication(guiMap, guiRoute,
		app::ApplicationConfig::getOptionalIntProperty("ViewportX", 400),
		app::ApplicationConfig::getOptionalIntProperty("ViewportY", 300),
		app::ApplicationConfig::getOptionalIntProperty("scrollSpeed", 10),
		app::ApplicationConfig::getOptionalIntProperty("screenWidth", 800),
		app::ApplicationConfig::getOptionalIntProperty("screenHeight", 600),
		app::ApplicationConfig::getOptionalIntProperty("screenBPP", 32),
		app::ApplicationConfig::getOptionalProperty("screenCaption", "-"),
		app::ApplicationConfig::getBooleanProperty("fullScreen")
	);

	this->guiApp->launch();

	delete this->guiApp;
	delete guiRoute;
	delete routeController;
	delete guiMap;
	delete qRoute;
	delete map;
}

util::Logger* Application::logger() {
	return util::LoggerManager::getInstance()->getLogger();
}

void Application::fillMissingParametersFromConfig(util::HashMap<std::string, std::string>* parameters) {
	int length = 5;
	std::string requiredValues[length];
	requiredValues[0] = "originName";
	requiredValues[1] = "originNumber";
	requiredValues[2] = "destinationName";
	requiredValues[3] = "destinationNumber";
	requiredValues[4] = "routeOptimize";
	for (int i = 0; i < length; i++) {
		if (!parameters->containsKey(&requiredValues[i])) {
			try {
				parameters->put(new std::string(requiredValues[i]), new std::string(app::ApplicationConfig::getProperty(requiredValues[i])));
			} catch (util::ElementNotFoundException e) {
				// El parametro puede no estar tampoco en el archivo de configuracion, en dicho caso
				logger()->warn("El parametro \"" + requiredValues[i] + "\" no se encontro ni por linea de comando ni por archivo.");
			}
		}
	}
}

}
